import os
import random
import shutil
import zipfile

from datetime import datetime
from inspect import isclass

from django import forms
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.db.models.query import QuerySet
from django.db.models.signals import post_init
from django.conf import settings
from django.core.files.base import ContentFile
from django.core.urlresolvers import reverse
from django.template.defaultfilters import slugify
from django.utils.functional import curry
from django.utils.translation import ugettext_lazy as _

# Required PIL classes may or may not be available from the root namespace
# depending on the installation method used.
try:
    import Image
    import ImageFile
    import ImageFilter
    import ImageEnhance
except ImportError:
    try:
        from PIL import Image
        from PIL import ImageFile
        from PIL import ImageFilter
        from PIL import ImageEnhance
    except ImportError:
        raise ImportError('DJangollery was unable to import the Python Imaging Library. Please confirm it`s installed and available on your current Python path.')

# attempt to load the django-tagging TagField from default location,
# otherwise we substitude a dummy TagField.
try:
    from tagging.fields import TagField
    tagfield_help_text = _('Separate tags with spaces, put quotes around multiple-word tags.')
except ImportError:
    class TagField(models.CharField):
        def __init__(self, **kwargs):
            default_kwargs = {'max_length': 255, 'blank': True}
            default_kwargs.update(kwargs)
            super(TagField, self).__init__(**default_kwargs)
        def get_internal_type(self):
            return 'CharField'
    tagfield_help_text = _('Django-tagging was not found, tags will be treated as plain text.')

from utils import EXIF


# Path to sample image
SAMPLE_IMAGE_PATH = getattr(settings, 'SAMPLE_IMAGE_PATH', os.path.join(os.path.dirname(__file__), 'res', 'sample.jpg')) # os.path.join(settings.PROJECT_PATH, 'photologue', 'res', 'sample.jpg'

# Modify image file buffer size.
ImageFile.MAXBLOCK = getattr(settings, 'DJANGOLLERY_MAXBLOCK', 256 * 2 ** 10)

# Photologue image path relative to media root
DJANGOLLERY_DIR = getattr(settings, 'DJANGOLLERY_DIR', 'djangollery')

# Look for user function to define file paths
DJANGOLLERY_PATH = getattr(settings, 'DJANGOLLERY_PATH', None)
if DJANGOLLERY_PATH is not None:
    if callable(DJANGOLLERY_PATH):
        get_storage_path = DJANGOLLERY_PATH
    else:
        parts = DJANGOLLERY_PATH.split('.')
        module_name = '.'.join(parts[:-1])
        module = __import__(module_name)
        get_storage_path = getattr(module, parts[-1])
else:
    def get_storage_path(instance, filename):
        return os.path.join(DJANGOLLERY_DIR, 'photos', filename)

# Quality options 
JPEG_QUALITY_VERY_LOW = 30
JPEG_QUALITY_LOW = 40
JPEG_QUALITY_MEDIUM_LOW = 50
JPEG_QUALITY_MEDIUM = 60
JPEG_QUALITY_MEDIUM_HIGHT = 70
JPEG_QUALITY_HIGHT = 80
JPEG_QUALITY_VERY_HIGHT = 90

# choices for new crop_anchor field in Photo
CROP_ANCHOR_CHOICES = (
    ('top', _('Top')),
    ('right', _('Right')),
    ('bottom', _('Bottom')),
    ('left', _('Left')),
    ('center', _('Center (Default)')),
)



# Prepare a list of image filters
filter_names = []
for n in dir(ImageFilter):
    klass = getattr(ImageFilter, n)
    if isclass(klass) and issubclass(klass, ImageFilter.BuiltinFilter) and \
        hasattr(klass, 'name'):
            filter_names.append(klass.__name__)
IMAGE_FILTERS_HELP_TEXT = _('Chain multiple filters using the following pattern "FILTER_ONE->FILTER_TWO->FILTER_THREE". Image filters will be applied in order. The following filters are available: %s.' % (', '.join(filter_names)))


class Gallery(models.Model):
    date_added = models.DateTimeField(_('date published'), default=datetime.now)
    title = models.CharField(_('title'), max_length=100, unique=True)
    title_slug = models.SlugField(_('title slug'), unique=True,
                                  help_text=_('A "slug" is a unique URL-friendly title for an object.'))
    description = models.TextField(_('description'), blank=True)
    is_public = models.BooleanField(_('is public'), default=True,
                                    help_text=_('Public galleries will be displayed in the default views.'))
    photos = models.ManyToManyField('Photo', related_name='galleries', verbose_name=_('photos'),
                                    null=True, blank=True)
    tags = TagField(help_text=tagfield_help_text, verbose_name=_('tags'))

    class Meta:
        ordering = ['-date_added']
        get_latest_by = 'date_added'
        verbose_name = _('gallery')
        verbose_name_plural = _('galleries')

    def __unicode__(self):
        return self.title

    def __str__(self):
        return self.__unicode__()

    def get_absolute_url(self):
        return reverse('pl-gallery', args=[self.title_slug])

    def latest(self, limit=0, public=True):
        if limit == 0:
            limit = self.photo_count()
        if public:
            return self.public()[:limit]
        else:
            return self.photos.all()[:limit]

    def sample(self, count=0, public=True):
        if count == 0 or count > self.photo_count():
            count = self.photo_count()
        if public:
            photo_set = self.public()
        else:
            photo_set = self.photos.all()
        return random.sample(photo_set, count)

    def photo_count(self, public=True):
        if public:
            return self.public().count()
        else:
            return self.photos.all().count()
    photo_count.short_description = _('count')

    def public(self):
        return self.photos.filter(is_public=True)

class SubclassingQuerySet(QuerySet):
    def __getitem__(self, k):
        result = super(SubclassingQuerySet, self).__getitem__(k)
        if isinstance(result, models.Model) :
            return result.as_leaf_class()
        else :
            return result
    def __iter__(self):
        for item in super(SubclassingQuerySet, self).__iter__():
            yield item.as_leaf_class()

class PhotoManager(models.Manager):
    def get_query_set(self):
        return SubclassingQuerySet(self.model)



class Photo(models.Model):
    title = models.CharField(_('title'), max_length=100, unique=True)
    title_slug = models.SlugField(_('slug'), unique=True,
                                  help_text=_('A "slug" is a unique URL-friendly title for an object.'))
    caption = models.TextField(_('caption'), blank=True)
    date_added = models.DateTimeField(_('date added'), default=datetime.now, editable=False)
    is_public = models.BooleanField(_('is public'), default=True, help_text=_('Public photographs will be displayed in the default views.'))
    tags = TagField(help_text=tagfield_help_text, verbose_name=_('tags'))
    content_type = models.ForeignKey(ContentType,editable=False,null=True)
    objects = PhotoManager()
    
    class Meta:
        ordering = ['-date_added']
        get_latest_by = 'date_added'
        verbose_name = _("photo")
        verbose_name_plural = _("photos")

    def __unicode__(self):
        return self.title

    def __str__(self):
        return self.__unicode__()

    def save(self, *args, **kwargs):
        if(not self.content_type): 
            self.content_type = ContentType.objects.get_for_model(self.__class__)
        if self.title_slug is None:
            self.title_slug = slugify(self.title)
        super(Photo, self).save(*args, **kwargs)

    def as_leaf_class(self):
        print "Hola"	
        content_type = self.content_type
        model = content_type.model_class()
        if(model == Photo):
            return self
        return model.objects.get(id=self.id)
    
    def get_absolute_url(self):
        return reverse('pl-photo', args=[self.title_slug])

    def public_galleries(self):
        """Return the public galleries to which this photo belongs."""
        return self.galleries.filter(is_public=True)

    def get_previous_in_gallery(self, gallery):
        try:
            return self.get_previous_by_date_added(galleries__exact=gallery,
                                                   is_public=True)
        except Photo.DoesNotExist:
            return None

    def get_next_in_gallery(self, gallery):
        try:
            return self.get_next_by_date_added(galleries__exact=gallery,
                                               is_public=True)
        except Photo.DoesNotExist:
            return None
	


class FlickrPhoto(Photo):
    thumbnail = models.URLField(_("thumbnail"))
    display = models.URLField(_("display"))
    original = models.URLField(_("original"))
        
    class Meta:
        verbose_name = _("flickr photo")
        verbose_name_plural = _("flickr photos")
    
    def get_thumbnail_url(self):
        return self.thumbnail
    
    def get_display_url(self):
        return self.display
        
    def get_original_url(self):
        return self.original
    
    def admin_thumbnail(self):
        return u'<a href="%s"><img src="%s"></a>' % \
                    (self.get_absolute_url(), self.get_thumbnail_url())  
    admin_thumbnail.short_description = _('Thumbnail')
    admin_thumbnail.allow_tags = True
        
    def save(self, *args, **kwargs):
        super(FlickrPhoto, self).save(*args, **kwargs)
    
    def delete(self, *args, **kwargs):
        super(FlickrPhoto, self).delete(*args, **kwargs)
    
    def public_galleries(self):
        """Return the public galleries to which this photo belongs."""
        return self.galleries.filter(is_public=True)


class FilePhoto(Photo):
    image = models.ImageField(_('image'), upload_to=get_storage_path)
    date_taken = models.DateTimeField(_('date taken'), null=True, blank=True, editable=False)
    view_count = models.PositiveIntegerField(default=0, editable=False)
    crop_from = models.CharField(_('crop from'), blank=True, max_length=10, default='center', choices=CROP_ANCHOR_CHOICES)


    class Meta:
        pass

    @property
    def EXIF(self):
        try:
            return EXIF.process_file(open(self.image.path, 'rb'))
        except:
            try:
                return EXIF.process_file(open(self.image.path, 'rb'), details=False)
            except:
                return {}
    
    def admin_thumbnail(self):
        func = getattr(self, 'get_admin_thumbnail_url', None)
        if func is None:
            return _('An "admin_thumbnail" photo size has not been defined.')
        else:
            if hasattr(self, 'get_absolute_url'):
                return u'<a href="%s"><img src="%s"></a>' % \
                    (self.get_absolute_url(), func())
            else:
                return u'<a href="%s"><img src="%s"></a>' % \
                    (self.image.url, func())
    admin_thumbnail.short_description = _('Thumbnail')
    admin_thumbnail.allow_tags = True

    def cache_path(self):
        return os.path.join(os.path.dirname(self.image.path), "cache")

    def cache_url(self):
        return '/'.join([os.path.dirname(self.image.url), "cache"])

    def image_filename(self):
        return os.path.basename(self.image.path)

    def _get_filename_for_size(self, size):
        base, ext = os.path.splitext(self.image_filename())
        return ''.join([base, '_', size, ext])
    
    def get_thumbnail_size(self):
        return Image.open(self._get_SIZE_filename('thumbnail')).size

    def get_display_size(self):
        return Image.open(self._get_SIZE_filename('display')).size
    
    def get_admin_thumbnail_url(self):
        return self.get_thumbnail_url()
    
    def get_thumbnail_url(self):
        return '/'.join([self.cache_url(), self._get_filename_for_size('thumbnail')])
    
    def get_display_url(self):
        self.increment_count()
        return '/'.join([self.cache_url(), self._get_filename_for_size('display')])
    
    def get_thumbnail_filename(self):
        return os.path.join(self.cache_path(),
                            self._get_filename_for_size('thumbnail'))
    
    def get_display_filename(self):
        return os.path.join(self.cache_path(),
                            self._get_filename_for_size('display'))
    
    def increment_count(self):
        self.view_count += 1
        models.Model.save(self)
    
    def size_exists(self, size):
        func = getattr(self, "get_%s_filename" % size, None)
        if func is not None:
            if os.path.isfile(func()):
                return True
        return False

    def resize_image(self, im, size, crop, upscale):
        cur_width, cur_height = im.size
        new_width, new_height = size
        if crop:
            ratio = max(float(new_width)/cur_width,float(new_height)/cur_height)
            x = (cur_width * ratio)
            y = (cur_height * ratio)
            xd = abs(new_width - x)
            yd = abs(new_height - y)
            x_diff = int(xd / 2)
            y_diff = int(yd / 2)
            if self.crop_from == 'top':
                box = (int(x_diff), 0, int(x_diff+new_width), new_height)
            elif self.crop_from == 'left':
                box = (0, int(y_diff), new_width, int(y_diff+new_height))
            elif self.crop_from == 'bottom':
                box = (int(x_diff), int(yd), int(x_diff+new_width), int(y)) # y - yd = new_height
            elif self.crop_from == 'right':
                box = (int(xd), int(y_diff), int(x), int(y_diff+new_height)) # x - xd = new_width
            else:
                box = (int(x_diff), int(y_diff), int(x_diff+new_width), int(y_diff+new_height))
            im = im.resize((int(x), int(y)), Image.ANTIALIAS).crop(box)
        else:
            if not new_width == 0 and not new_height == 0:
                ratio = min(float(new_width)/cur_width,
                            float(new_height)/cur_height)
            else:
                if new_width == 0:
                    ratio = float(new_height)/cur_height
                else:
                    ratio = float(new_width)/cur_width
            new_dimensions = (int(round(cur_width*ratio)),
                              int(round(cur_height*ratio)))
            if new_dimensions[0] > cur_width or \
               new_dimensions[1] > cur_height:
                if not upscale:
                    return im
            im = im.resize(new_dimensions, Image.ANTIALIAS)
        return im

    def create_size(self, sizename,size,crop,upscale,quality):
        if self.size_exists(sizename):
            return
        if not os.path.isdir(self.cache_path()):
            os.makedirs(self.cache_path())
        try:
            im = Image.open(self.image.path)
        except IOError:
            return
        # Save the original format
        im_format = im.format
        print im_format
        # Resize/crop image
        if im.size != size and size != (0, 0):
            im = self.resize_image(im, size,crop,upscale)        
        # Save file
        im_filename = getattr(self, "get_%s_filename" % sizename)()Å
        try:
            if im_format != 'JPEG':
                try:
                    im.save(im_filename)
                    return
                except KeyError:
                    pass
            im.save(im_filename, 'JPEG', quality=int(quality), optimize=True)
        except IOError, e:
            if os.path.isfile(im_filename):
                os.unlink(im_filename)
            raise e

    def remove_size(self, size, remove_dirs=True):
        if not self.size_exists(size):
            return
        filename = getattr(self, "get_%s_filename" % size)()
        if os.path.isfile(filename):
            os.remove(filename)
        if remove_dirs:
            self.remove_cache_dirs()

    def clear_cache(self):
        self.remove_size('thumbnail', False)
        self.remove_size('display', False)
        self.remove_cache_dirs()

    def pre_cache(self):
        self.create_size('thumbnail',[100,0],False,False,JPEG_QUALITY_MEDIUM)
        self.create_size('display',[500,0],False,False,JPEG_QUALITY_HIGHT)

    def remove_cache_dirs(self):
        try:
            os.removedirs(self.cache_path())
        except:
            pass
    
    def get_original_url(self):
        return self.image.url
    
    def save(self, *args, **kwargs):
        print self.image.path
        if self.date_taken is None:
            try:
                exif_date = self.EXIF.get('EXIF DateTimeOriginal', None)
                if exif_date is not None:
                    d, t = str.split(exif_date.values)
                    year, month, day = d.split(':')
                    hour, minute, second = t.split(':')
                    self.date_taken = datetime(int(year), int(month), int(day),
                                               int(hour), int(minute), int(second))
            except:
                pass
        if self.date_taken is None:
            self.date_taken = datetime.now()
        super(FilePhoto, self).save(*args, **kwargs)
        if self._get_pk_val():
            self.clear_cache()
        self.pre_cache()

        

    def delete(self):
        assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
        self.clear_cache()
        super(FilePhoto, self).delete()

